-----------The Latin Hangman-----------
A 4am crack                  2020-04-10
-------------------. updated 2021-12-22
                   |___________________

Name: The Latin Hangman
Version: revision 1004
Genre: educational
Year: 1983
Credits: George Earl
Platform: Apple ][ (32K) or later
Media: 5.25-inch disk
Sides: 2
OS: custom
Previous cracks: none

Every other Friday, the entire class
would trek down to the computer lab. We
would break into groups of 2 or 3,
because we did not have enough disks
for everyone. One week, I smuggled in a
bit copy program, and -- heart racing,
palms sweaty -- I made a protected
backup. That backup disk survived into
the modern era, but by the time I had
the tools to digitize it, it had
succumbed to the ravages of time. I
looked online but found no traces of
this obscure educational software. I
assumed it was lost forever, and with
it, a small but memorable piece of my
childhood.

Nine years later -- mirabile dictu --
it appeared before me: online auction,
"Buy It Now," immediate shipping. And
reasonably priced, because honestly,
how could a game like this be important
to anyone?

This is that game.

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  can't read anything beyond T00,S00

EDD 4 bit copy (no sync, no count)
  works

Copy ][+ 8.4 nibble editor
  appears to be 13-sector, 5-3 encoded
  tracks 0-2 are standard but tracks 3+
  use modified prologues and epilogues
  address
    prologue "D5 BB B5"
    epilogue "DE BB EB"
  data
    prologue "D5 BB AD"
    epilogue "DE BB EB"

Copy ][+ 8.4 sector editor
  ["P" for sector editor patcher]
    ["DOS 3.2"]
    ["CUSTOM"]
    ["PROLOG" = "D5 BB B5" (address)]
    ["EPILOG" = "DE BB" (address)]
    ["PROLOG" = "D5 BB AD" (data)]
    ["EPILOG" = "DE BB" (data)]
  Success! T03+ readable
  T11 -> looks like disk catalog

Why didn't COPYA work?
  not a 16-sector disk

Why didn't Locksmith FDB work?
  not a 16-sector disk

EDD worked. What does that tell us?
  no half or quarter tracks
  almost certainly no nibble check
  just structural changes to prologues/
    epilogues

Next steps:

  1. Capture 13-sector RWTS with
     AUTOTRACE
  2. Convert to 16-sector format with
     Advanced Demuffin
  3. Copy files to freshly formatted
     DOS 3.3 disk
  4. Declare victory(*)

(*) do not go to the gym, stay the f---
    home

                   ~

               Chapter 1
  In Which It's The End of The World
    As We Know It, And I Feel Like
  The Music of My Childhood Instilled
   Unrealistic Expectations of How I
          Would Feel About It


Passport has some ability to convert
13-sector disks, but in this case it
decides the bootloader is too unusual
and bails. So we're going to dust off
my old work disk with my old AUTOTRACE
program.

[S6,D1=original disk]
[S5,D1=my work disk]

]PR#5
CAPTURING BOOT0
...reboots slot 6...
...reboots slot 5...
SAVING BOOT0
/!\ BOOT0 RELOCATES TO $0200
CAPTURING BOOT0 STAGE 2
...reboots slot 6...
...reboots slot 5...
SAVING BOOT0 STAGE 2
CAPTURING BOOT1
...reboots slot 6...
...reboots slot 5...
SAVING BOOT1
SAVING RWTS
/!\ 13-SECTOR RWTS

Here's what it captured automatically.
(This is the standard "boot a 13-sector
disk on a 16-sector drive" bootloader.
If you know how that works, you can
skip to Chapter 2.)

]BLOAD BOOT0,A$800
]CALL -151

*801L

; immediately move this code to the
; input buffer at $0200
0801-   A0 0F       LDY   #$0F
0803-   B9 00 08    LDA   $0800,Y
0806-   99 00 02    STA   $0200,Y
0809-   C8          INY
080A-   D0 F7       BNE   $0803
080C-   4C 0F 02    JMP   $020F

*20F<80F.8FFM

*20FL

; set up nibble translation table
020F-   A0 AB       LDY   #$AB
0211-   98          TYA
0212-   85 3C       STA   $3C
0214-   4A          LSR
0215-   05 3C       ORA   $3C
0217-   C9 FF       CMP   #$FF
0219-   D0 09       BNE   $0224
021B-   C0 D5       CPY   #$D5
021D-   F0 05       BEQ   $0224
021F-   8A          TXA
0220-   99 00 08    STA   $0800,Y
0223-   E8          INX
0224-   C8          INY
0225-   D0 EA       BNE   $0211

; $00 into zero page $26 and $03 into
; $27 means we're probably going to be
; loading data into $0300..$03FF soon.
0227-   84 3D       STY   $3D
0229-   84 26       STY   $26
022B-   A9 03       LDA   #$03
022D-   85 27       STA   $27
022F-   A6 2B       LDX   $2B
0231-   20 5D 02    JSR   $025D

*25DL

; read a sector from track $00 (this is
; similar to the disk controller ROM
; routine at $C65C, but it looks for an
; address prologue of "D5 AA B5" and
; uses the 5-3 encoding translation
; table we set up earlier at $020F
025D-   18          CLC
025E-   08          PHP
025F-   BD 8C C0    LDA   $C08C,X
0262-   10 FB       BPL   $025F
0264-   49 D5       EOR   #$D5
0266-   D0 F7       BNE   $025F
0268-   BD 8C C0    LDA   $C08C,X
026B-   10 FB       BPL   $0268
026D-   C9 AA       CMP   #$AA
026F-   D0 F3       BNE   $0264
0271-   EA          NOP
0272-   BD 8C C0    LDA   $C08C,X
0275-   10 FB       BPL   $0272
0277-   C9 B5       CMP   #$B5    <-- !
0279-   F0 09       BEQ   $0284
027B-   28          PLP
027C-   90 DF       BCC   $025D
027E-   49 AD       EOR   #$AD
0280-   F0 1F       BEQ   $02A1
0282-   D0 D9       BNE   $025D
...[snipped]...

; store partially decoded data at $0300
; (target address set at $0229)
02C1-   91 26       STA   ($26),Y
02C3-   C8          INY
02C4-   D0 EF       BNE   $02B5
02C6-   BC 8C C0    LDY   $C08C,X
02C9-   10 FB       BPL   $02C6
02CB-   59 00 08    EOR   $0800,Y
02CE-   D0 8D       BNE   $025D
02D0-   60          RTS

*234L

; finish decoding nibbles (not shown)
0234-   20 D1 02    JSR   $02D1

; Weird. This code is modifying the
; code it just read from disk. Not sure
; why yet.
0237-   A9 A9       LDA   #$A9
0239-   8D 1F 03    STA   $031F
023C-   A9 02       LDA   #$02
023E-   8D 20 03    STA   $0320

; jump to the code we just read
0241-   4C 01 03    JMP   $0301

At this point, AUTOTRACE interrupts the
boot and captures the sector we just
read into $0300. Let's see what that
looks like.

*BLOAD BOOT0 0300-03FF,A$300
*301L

; data table fiddling
0301-   B9 00 08    LDA   $0800,Y
0304-   0A          ASL
0305-   0A          ASL
0306-   0A          ASL
0307-   99 00 08    STA   $0800,Y
030A-   C8          INY
030B-   D0 F4       BNE   $0301

; slot number (x16)
030D-   A6 2B       LDX   $2B

; looks like we're loading the next
; phase into $0900
030F-   A9 09       LDA   #$09
0311-   85 27       STA   $27

; this should be the address into which
; we're loading the next phase of the
; boot
0313-   AD CC 03    LDA   $03CC
0316-   85 41       STA   $41

*3CC

03CC- 76

Fantastic. (By the way, this is why
Passport refused to continue tracing
and capture this disk's RWTS -- because
it would load at $7600 and overwrite
Passport itself! Passport leaves room
at $3600 and $B600 for capturing a
disk's RWTS; this disk uses neither.)

0318-   84 40       STY   $40
031A-   8A          TXA
031B-   4A          LSR
031C-   4A          LSR
031D-   4A          LSR
031E-   4A          LSR

; This is the instruction that got
; replaced at $023B. Looking at the
; previous few instructions (which
; are useless because this modified
; instruction blows away the contents
; of the accumulator), it appears that
; this code can be used to re-use the
; disk controller ROM routine to read
; sectors, but has been modified to use
; the routine at $025D instead.
031F-   A9 02       LDA   #$02
0321-   85 3F       STA   $3F
0323-   A9 5D       LDA   #$5D
0325-   85 3E       STA   $3E

; read a sector via ($3E) (so, $025D)
0327-   20 43 03    JSR   $0343

; decode nibble data
032A-   20 46 03    JSR   $0346

*346L

0346-   A2 32       LDX   #$32
0348-   A0 00       LDY   #$00
034A-   BD 00 08    LDA   $0800,X
034D-   4A          LSR
034E-   4A          LSR
034F-   4A          LSR
0350-   85 3C       STA   $3C
0352-   4A          LSR
0353-   85 2A       STA   $2A
0355-   4A          LSR

; Ah, I see what's going on now. The
; routine at $025D stores raw data in
; the target page ($0900), but doesn't
; decode it. This routine now takes
; that data and decodes it (using the
; data table we set up at $0800), then
; it stores the final data in ($40).
; ($40) was initialized as $00/<$03CC>,
; which is $76. So this is storing data
; at $7600 and up.
0356-   1D 00 09    ORA   $0900,X
0359-   91 40       STA   ($40),Y
035B-   C8          INY
035C-   BD 33 08    LDA   $0833,X
035F-   4A          LSR
0360-   4A          LSR
0361-   4A          LSR
0362-   4A          LSR
0363-   26 3C       ROL   $3C
0365-   4A          LSR
0366-   26 2A       ROL   $2A
0368-   1D 33 09    ORA   $0933,X
036B-   91 40       STA   ($40),Y
036D-   C8          INY
036E-   BD 66 08    LDA   $0866,X
0371-   4A          LSR
0372-   4A          LSR
0373-   4A          LSR
0374-   4A          LSR
0375-   26 3C       ROL   $3C
0377-   4A          LSR
0378-   26 2A       ROL   $2A
037A-   1D 66 09    ORA   $0966,X
037D-   91 40       STA   ($40),Y
037F-   C8          INY
0380-   A5 2A       LDA   $2A
0382-   29 07       AND   #$07
0384-   1D 99 09    ORA   $0999,X
0387-   91 40       STA   ($40),Y
0389-   C8          INY
038A-   A5 3C       LDA   $3C
038C-   29 07       AND   #$07
038E-   1D CC 09    ORA   $09CC,X
0391-   91 40       STA   ($40),Y
0393-   C8          INY
0394-   CA          DEX
0395-   10 B3       BPL   $034A
0397-   AD 99 08    LDA   $0899
039A-   4A          LSR
039B-   4A          LSR
039C-   4A          LSR
039D-   0D FF 09    ORA   $09FF
03A0-   91 40       STA   ($40),Y
03A2-   A6 2B       LDX   $2B
03A4-   60          RTS

Continuing from $032D...

*32DL

; Zero page $3D is a counter for the
; number of sectors read already. $03FF
; is the total number of sectors.
032D-   A5 3D       LDA   $3D
032F-   4D FF 03    EOR   $03FF

; if we've read enough sectors, break
; out of the loop
0332-   F0 06       BEQ   $033A

*3FF

03FF- 09

We're reading $0A sectors. (The
comparison happens before the count is
incremented, so add 1.)

; increment target page (starts at $76)
0334-   E6 41       INC   $41

; increment sector count
0336-   E6 3D       INC   $3D

; this is really an unconditional jump
0338-   D0 ED       BNE   $0327

; Break out of loop here (from $0332).
; Accumulator is always zero at this
; point.
033A-   85 3E       STA   $3E

; still $76
033C-   AD CC 03    LDA   $03CC
033F-   85 3F       STA   $3F

; now $77
0341-   E6 3F       INC   $3F

; jump to $7700 to continue the boot
0343-   6C 3E 00    JMP   ($003E)

At this point, AUTOTRACE interrupts the
boot again by changing the instruction
at $033C to jump to a callback that
captures the new code at $7600..$7FFF,
stored on my work disk as BOOT1.

                   ~

               Chapter 2
   In Which Everything Is Off By 16K


*BLOAD BOOT1,A$7600
*7700L

; well, would you look at that -- we've
; managed to load a more-or-less normal
; DOS-shaped bootloader in low memory
7700-   8E E9 77    STX   $77E9
7703-   8E F7 77    STX   $77F7
7706-   A9 01       LDA   #$01
7708-   8D F8 77    STA   $77F8
770B-   8D EA 77    STA   $77EA
770E-   AD E0 77    LDA   $77E0
7711-   8D E1 77    STA   $77E1
7714-   A9 00       LDA   #$00
7716-   8D EC 77    STA   $77EC
7719-   AD E2 77    LDA   $77E2
771C-   8D ED 77    STA   $77ED
771F-   AD E3 77    LDA   $77E3
7722-   8D F1 77    STA   $77F1

*77E2

77E2- 0B

This code starts reading at track $00
(hard-coded), sector $0B (from $77E2).

*77E3

77E3- 5D

Into $5D00.

*77E0

77E0- 19

$19 sectors, so $5D00..$75FF. Basically
a full copy of DOS, just below the RWTS
and bootloader at $7600.

; pretty standard stuff to set up DOS
; pointers
7725-   A9 01       LDA   #$01
7727-   8D F4 77    STA   $77F4
772A-   8A          TXA
772B-   4A          LSR
772C-   4A          LSR
772D-   4A          LSR
772E-   4A          LSR
772F-   AA          TAX
7730-   A9 00       LDA   #$00
7732-   9D F8 04    STA   $04F8,X
7735-   9D 78 04    STA   $0478,X

; read DOS into memory
7738-   20 93 77    JSR   $7793

; standard machine initialization stuff
773B-   A2 FF       LDX   #$FF
773D-   9A          TXS
773E-   8E EB 77    STX   $77EB
7741-   20 93 FE    JSR   $FE93
7744-   20 89 FE    JSR   $FE89

; jump to DOS cold start entry point
7747-   4C 84 5D    JMP   $5D84

Okay so far. But remember, tracks 3+
use modified prologues, so at some
point it's going to switch those.

Also, the original disk does not ever
show the DOS prompt (which would happen
during the DOS cold boot at $5D84), so
there may be further shenanigans ahead.

Looking at the RWTS entry point at
$7D00, an unusual JSR jumps out at me.

*7D00L

7D00-   84 48       STY   $48
7D02-   85 49       STA   $49
7D04-   A0 01       LDY   #$01
7D06-   B1 48       LDA   ($48),Y
7D08-   AA          TAX
7D09-   20 A1 7E    JSR   $7EA1   <-- !

*7EA1L

; instruction that I expected at $7D09
7EA1-   8C F8 04    STY   $04F8

; look at track
7EA4-   AD EC 77    LDA   $77EC

; tracks 0-2 treated one way
7EA7-   F0 0C       BEQ   $7EB5
7EA9-   C9 01       CMP   #$01
7EAB-   F0 08       BEQ   $7EB5
7EAD-   C9 02       CMP   #$02
7EAF-   F0 04       BEQ   $7EB5

; higher tracks treated another way --
; by changing the address and data
; prologues for both read and write
7EB1-   A9 BB       LDA   #$BB
7EB3-   D0 02       BNE   $7EB7
7EB5-   A9 AA       LDA   #$AA
7EB7-   8D 98 78    STA   $7898
7EBA-   8D E3 78    STA   $78E3
7EBD-   8D 12 79    STA   $7912
7EC0-   8D 60 79    STA   $7960
7EC3-   8D 80 79    STA   $7980
7EC6-   8D BC 79    STA   $79BC
7EC9-   8A          TXA
7ECA-   60          RTS

That explains how the disk is able to
read itself, despite having different
prologues for lower and higher tracks.
On every sector read, it looks at the
track and changes the RWTS code
accordingly.

One thing of note (because this is
going to trip me up later): standard
RWTS code is designed to be agnostic
about where the RWTS parameter table
is. It takes an address in A & Y and
stores it in zero page $48/$49, then
uses indirect addressing to find values
within the table. But this custom code
at $7EA1 does not do that; it assumes
the RWTS parameter table is $77E8 and
the track number is at $77EC.

                   ~

               Chapter 3
  In Which We Attempt To Use The Disk
      As A Weapon Against Itself
   And It Goes Extraordinarily Well
       Right Up Until It Doesn't


Before I can use Advanced Demuffin with
this captured RWTS, I will create an
IOB file. (See the docs on my work disk
for more about IOB files.) Advanced
Demuffin assumes the RWTS entry point
is at $BD00, but this one is at $7D00.

Thus:

; Most of this is identical to the
; standard IOB module that comes with
; Advanced Demuffin
1400-   4A          LSR
1401-   8D 22 0F    STA   $0F22
1404-   8C 23 0F    STY   $0F23
1407-   8E 27 0F    STX   $0F27
140A-   A9 01       LDA   #$01
140C-   8D 20 0F    STA   $0F20
140F-   8D 2A 0F    STA   $0F2A

; One problem with having an RWTS at
; $7800..$7FFF is that that range is
; normally used to store track data
; during the copy process. If we just
; let Advanced Demuffin run, it will
; overwrite the custom RWTS almost
; immediately and crash. In the
; ADVANCED DEMUFFIN 1.5 DOCS (also
; included on my work disk), it
; mentions that you can control how
; many sectors Advanced Demuffin reads
; at a time, and where it puts it in
; memory. Normally $1CF0 is $20 and
; $1CF1 is $90, meaning that it will
; copy 7 tracks worth of data at a time
; into $2000..$8FFF. Changing the end
; parameter to $76 will only copy one
; track at a time, but has the distinct
; advantage of not overwriting the RWTS
; and crashing.
1412-   A9 76       LDA   #$76
1414-   8D F1 1C    STA   $1CF1

; The next problem, specific to this
; disk and its custom RWTS swapper at
; $7EA1, is that it assumes the RWTS
; parameter table is at $77E8. Since
; that is not the case, we get to copy
; Advanced Demuffin's RWTS parameter
; table into the place that the RWTS
; swapper expects it. Otherwise it
; will incorrectly determine which
; track we're reading and set the
; prologue bytes to the wrong values.
1417-   A0 10       LDY   #$10
1419-   B9 1E 0F    LDA   $0F1E,Y
141C-   99 E8 77    STA   $77E8,Y
141F-   88          DEY
1420-   10 F7       BPL   $1419

; Finally, get the address of the RWTS
; parameter table at $0F1E and call the
; RWTS entry point at $7D00 (instead of
; the usual $BD00)
1422-   A9 0F       LDA   #$0F
1424-   A0 1E       LDY   #$1E
1426-   4C 00 7D    JMP   $7D00

*BSAVE IOB,A$1400,L$FB

Now I can use Advanced Demuffin to
convert the disk to a standard format.
It uses the disk's own RWTS to read the
original (stored in the RWTS file,
accessed via the IOB module), then a
standard DOS 3.3-compatible RWTS to
write out the data, sector by sector.

[S6,D1=original disk]
[S6,D2=blank disk]
[S5,D1=my work disk]

]BRUN ADVANCED DEMUFFIN 1.5

[press "5" to switch to slot 5]

[press "R" to load a new RWTS module]
  --> At $78, load "RWTS" from drive 1

[press "I" to load a new IOB module]
  --> load "IOB" from drive 1

[press "6" to switch to slot 6]

[press "C" to convert disk]

[press "Y" to change default values]

                 --v--

ADVANCED DEMUFFIN 1.5    (C) 1983, 2014
ORIGINAL BY THE STACK    UPDATES BY 4AM
=======================================


INPUT ALL VALUES IN HEX


SECTORS PER TRACK? (13/16) 13 <--change

START TRACK: $00
START SECTOR: $00
END TRACK: $22
END SECTOR: $0C

INCREMENT: 1

MAX # OF RETRIES: 0

COPY FROM DRIVE 1
TO DRIVE: 2
=======================================
13SC $00,$00-$22,$0C BY$01 S6,D1->S6,D2

                 --^--

[press RETURN to start copy]

                 --v--

ADVANCED DEMUFFIN 1.5    (C) 1983, 2014
ORIGINAL BY THE STACK    UPDATES BY 4AM
=======PRESS ANY KEY TO CONTINUE=======
TRK:R..................................
+.5:
    0123456789ABCDEF0123456789ABCDEF012
SC0:...................................
SC1:...................................
SC2:...................................
SC3:...................................
SC4:...................................
SC5:...................................
SC6:...................................
SC7:...................................
SC8:...................................
SC9:...................................
SCA:R..................................
SCB:...................................
SCC:...................................



=======================================
13SC $00,$00-$22,$0C BY$01 S6,D1->S6,D2

                 --^--

The read error on T00,S0A is expected;
this is where it stores a copy of the
16-sector boot sector on the otherwise
13-sector disk.

Of course the demuffin'd disk is not
bootable, since its bootloader is still
expecting 5-and-3 encoded nibbles with
custom prologues. We'll deal with that
in a moment. But look, a disk catalog:

]PR#5
...
]CATALOG,S6,D2

C1983 DSR^C#254
056 FREE

 A 002 MENU
 I 054 APPLESOFT
 B 048 YY
 B 008 S1
 B 010 S2
 B 010 S3
 B 010 S4
 B 010 S5
 B 011 S6
 B 015 S11
 B 011 V1
 B 011 V2
 B 012 V3
 B 013 V9
 B 011 S7
 B 012 V4
 B 011 S8
 B 013 V5
 B 012 V6
 B 012 S12
 B 013 S9
 B 014 S10
 B 012 V7
 B 012 V8

]RUN MENU

                 --v--

COPYRIGHT 1983 BY GEORGE EARL


1302 S. GEN. MCMULLEN, SAN ANTONIO,TEXAS


ALL RIGHTS RESERVED -- AS A COMPETENT

PROGRAMMER, YOU SHOULD TRY TO RESPECT

OTHER PROGRAMMER'S RIGHTS.

                 --^--

Oh.

                   ~

               Chapter 4
       In Which We Pretend To Be
        A Competent Programmer


It appears that copying the files is
not going to be sufficient, which means
I get to trace the boot further to
determine how it's loading the real
program.

Returning to my demuffin'd disk, I have
a disk with a 13-sector RWTS and a
custom RWTS swapper, none of which is
useful anymore. I would like to replace
it with a standard DOS 3.3 bootloader
located at the same memory address.

This is more difficult than it sounds.
The bootloader loads at $7600..$7FFF,
which is the top of memory for a 32K
Apple ][. Aha! The DOS 3.3 master disk
will load at $3600 then relocate itself
to the top of memory. On a 32K machine,
the top of memory would be $7FFF.

Except... I don't actually own a 32K
Apple ][. Looking around my office, I
can see

 - Apple IIgs (min. 256K)
 - Pravetz 8C (min. 128K)
 - Apple //e (min. 64K)
 - Apple ][+ (min. 48K)

Ultimately, I discovered that the
Virtual ][ emulator can emulate an
original Apple ][ with a selectable
amount of memory, including 32K. So I
set that up, booted the DOS 3.3 master
disk, captured the relocated RWTS, and
copied it to track 0 on my crack-in-
progress.

[S6,D1=DOS 3.3 master disk]
[S5,D1=my work disk]

]PR#6
...
LOADING APPLESOFT BASIC INTO MEMORY
  (this is not relevant to the crack,
   but I have literally never seen
   this message in 40 years of using
   Apple ][s, and it's really weird)

>FP

[S6,D1=blank disk]

]INIT HELLO
...write write write...

Turning to my trusty Disk Fixer sector
editor, I copied sectors 0 and 2-9 of
track 0 from this newly initialized DOS
3.3 disk to my crack-in-progress.
Sector 1 has the code to load DOS from
the first 13 sectors of each track,
which is still where it is even though
this is now a 16-sector disk.

Now... will it be enough?

[S6,D1=crack-in-progress]

]PR#6
...boots, loads DOS, hangs...

Sigh.

                   ~

               Chapter 5
    In Which Competence Is Relative


It sounds like my crack-in-progress is
successfully loading DOS. To quickly
verify that, I changed the "JMP $5D84"
on sector 1 to "JMP $FF59". Sure
enough, it breaks to the monitor. That
means the RWTS is in place, and it's
working, and it's loading DOS into the
right place ($5D00+). So there is
further protection code beyond that.

*5D84L

5D84-   AD E9 77    LDA   $77E9
5D87-   4A          LSR
5D88-   4A          LSR
5D89-   4A          LSR
5D8A-   4A          LSR
5D8B-   8D 6A 6A    STA   $6A6A
5D8E-   AD EA 77    LDA   $77EA
5D91-   8D 68 6A    STA   $6A68
5D94-   20 34 63    JSR   $6334

That is not normal. Let's see what's
at $6334.

*6334L

6334-   20 B1 7E    JSR   $7EB1

Uh oh. That's inside the RWTS. But
we just replaced the RWTS... oh dear.
The original disk has custom code
inside the RWTS we just replaced.

Rebooting my work disk, I can check
the RWTS I originally captured to see
what's at $7EB1.

]PR#5
...
]BLOAD RWTS,A$7800
]CALL -151

*7EB1L

7EB1-   A9 BB       LDA   #$BB
7EB3-   D0 02       BNE   $7EB7
7EB5-   A9 AA       LDA   #$AA
7EB7-   8D 98 78    STA   $7898
7EBA-   8D E3 78    STA   $78E3
7EBD-   8D 12 79    STA   $7912
7EC0-   8D 60 79    STA   $7960
7EC3-   8D 80 79    STA   $7980
7EC6-   8D BC 79    STA   $79BC
7EC9-   8A          TXA
7ECA-   60          RTS

Ah, this is the RWTS swapper that I
discovered earlier. Not sure why we're
calling it again though.

]PR#6
...still breaks into monitor with DOS
   in memory...

]CALL -151

Continuing from $6337...

; copy something into place (more on
; this in a moment)
6337-   A2 2C       LDX   #$2C
6339-   BD 13 64    LDA   $6413,X
633C-   9D A0 7E    STA   $7EA0,X
633F-   CA          DEX
6340-   D0 F7       BNE   $6339

; restore code at $7D09 that previously
; called the RWTS swapper
6342-   A9 8C       LDA   #$8C
6344-   8D 09 7D    STA   $7D09
6347-   A9 F8       LDA   #$F8
6349-   8D 0A 7D    STA   $7D0A

; also restore the code at $5D94 that
; called this routine
634C-   8D 95 5D    STA   $5D95
634F-   A9 04       LDA   #$04
6351-   8D 0B 7D    STA   $7D0B
6354-   A9 AD       LDA   #$AD
6356-   8D 94 5D    STA   $5D94

And this is why we called the RWTS
swapper one last time -- to make the
changes permanent, then reuse the space
in memory and remove the call to the
RWTS swapper on every sector read.

So what's at $7EA0 now?

*6413L
[...omitted for brevity, but it appears
    to be the actual 13-sector RWTS
    code that was supposed to be at
    $7EA0 in the first place...]

To sum up, this subroutine does two
things, neither of which are desirable:

 1. call the RWTS swapper one last time
    (don't want that)
 2. replace part of the RWTS with the
    "original" RWTS code, but of a 13-
    sector RWTS, not a 16-sector RWTS
    (don't want that either)

Continuing from $6359, we have this:

6359-   20 BC 63    JSR   $63BC
635C-   60          RTS

*63BCL

63BC-   20 58 FC    JSR   $FC58
63BF-   A2 15       LDX   #$15
63C1-   BD A2 63    LDA   $63A2,X
63C4-   9D 30 04    STA   $0430,X
63C7-   CA          DEX
63C8-   D0 F7       BNE   $63C1
63CA-   A2 0D       LDX   #$0D
63CC-   BD D5 63    LDA   $63D5,X
63CF-   9D 34 07    STA   $0734,X
63D2-   CA          DEX
63D3-   D0 F7       BNE   $63CC
63D5-   60          RTS

*63BCG
[...prints "THE LATIN HANGMAN" loading
    screen...]

OK, that part is actually useful. I can
change the JSR at $5D94 to call that
directly and skip over the other stuff.

T00,S0B,$95: 34 -> BC

(I also changed the JMP $FF59 for
debugging back to JMP $5D84.)

]PR#6
...boots, prints title screen, hangs...

Progress!

                   ~

               Chapter 6
In Which All That Glitters Is Not Gold,
   All That Prints Is Not Specious,
    And Everything Is Actually Fine
              In The End


Continuing from the first instruction
after the JSR $63BC (which prints the
text title screen)...

; normal
5D97-   A9 00       LDA   #$00
5D99-   D0 11       BNE   $5DAC
5D9B-   8D B6 6A    STA   $6AB6
5D9E-   A2 0A       LDX   #$0A
5DA0-   BD 61 5D    LDA   $5D61,X
5DA3-   9D 55 5D    STA   $5D55,X
5DA6-   CA          DEX
5DA7-   D0 F7       BNE   $5DA0
5DA9-   4C BC 5D    JMP   $5DBC
...

; normal
5DBC-   38          SEC
5DBD-   B0 12       BCS   $5DD1
...

; normal
5DD1-   08          PHP

; hook I/O vectors at ($36) and ($38)
; (normal)
5DD2-   20 51 68    JSR   $6851
5DD5-   A9 00       LDA   #$00
5DD7-   8D 5E 6A    STA   $6A5E
5DDA-   8D 52 6A    STA   $6A52
5DDD-   28          PLP
5DDE-   6A          ROR
5DDF-   8D 51 6A    STA   $6A51

; mormal (always branches)
5DE2-   30 03       BMI   $5DE7
...

5DE7-   6C 5C 5D    JMP   ($5D5C)

$5D5C/$5D5D were just overwritten in
the copy loop at $5D9E, with the values
at $5D68/$5D69.

*5D68.5D69

5D68- 81 62

*6281L

; pop return value (so we're not
; returning to the normal bootloader)
6281-   68          PLA
6282-   68          PLA

; overwrite all of zero page with
; values that (I am not making this up)
; were stored in the original RWTS
6283-   A2 00       LDX   #$00
6285-   BD D0 7E    LDA   $7ED0,X
6288-   95 00       STA   $00,X
628A-   E8          INX
628B-   D0 F8       BNE   $6285

; print a space, wait what?
628D-   A9 A0       LDA   #$A0
628F-   20 ED FD    JSR   $FDED

Let's back up.

We're overwriting the entire zero page,
which means we're also overwriting the
I/O vectors that $FDED routes through.
I always think about $FDED as "the
print function," but it's literally
just JMP ($0036).

$7ED0 + $0036 = $7F06. Let's see where
that JMP $FDED is really going.

]PR#5
...

]BLOAD RWTS,A$7800
]CALL -151

*7F06.7F07

7F06- 81 5E

Oh! This is actually fine, like non-
sarcastically fine. $5E81 is the DOS
keyboard intercept routine. On an
unprotected DOS 3.3 disk, it would be
called later by way of the I/O vectors
being hooked and catching the printing
of "]" and the subsequent attempt to
read the keyboard after initializing
BASIC. (This part of the DOS boot is
extremely weird because of the way
BASIC is initialized. It all seems
super suspicious but is actually not.)

The strange part is that, instead of
being called via the input vector, this
disk is setting it as the output vector
(by way of overwriting the entire zero
page, of which more in a moment) and
"printing" a space character. But the
end result is the same: the boot
continues as normal. The only thing to
actually worry about here is how to
overwrite zero page.

I have an idea for that: there is a
scratch buffer (normally at $BB00, here
at $7B00) that is read from track 0,
sector 5, then overwritten during
subsequent RWTS calls. This scratch
buffer is $156 bytes long, which is
more than enough for our purposes.

Using my trusty sector editor, I copied
the buffer that starts at T00,S08,$D0
on the original disk, into T00,S05,$00
of my crack-in-progress. It looks like
this (in part):

                 --v--

-------------- DISK EDIT --------------
TRACK $00/SECTOR $05/VOLUME $FE/BYTE$00
---------------------------------------
$00:>40<00 00 00 00 00 00 00   @@@@@@@@
$08: 00 00 00 00 00 00 00 00   @@@@@@@@
$10: 00 00 00 00 00 00 00 E3   @@@@@@@c
$18: 40 04 44 00 00 00 00 00   @DD@@@@@
$20: 00 28 00 18 01 17 D0 05   @(@XAWPE
$28: D0 07 D0 07 00 00 80 02   PGPG@@.B
$30: 00 00 FF BE 05 00 81 5E   @@.>E@.^
$38: BD 5E 40 02 40 02 40 02   =^@B@B@B
$40: 40 02 A2 5B 82 58 EF D8   @B"[.XoX
$48: 00 77 00 08 00 C0 51 BE   @7@H@@Q>
$50: 65 8C 09 C3 CF D0 D9 D2   %.ICOPYR
$58: C9 C7 C8 D4 A0 B1 B9 B7   IGHT 197
$60: B8 A0 C2 D9 A0 C7 C5 60   8 BY GE
$68: 62 00 60 54 01 B2 0D 94   "@ TA2M.
$70: EC 07 ED ED EC EC ED ED   lGmmllmm
$78: 62 8C 0D 0D 0D 0D 0D 0D   ".MMMMMM
...

                 --^--

Oh cute, a copyright message from the
developer (George Earl). Apparently
this bootloader dates back to 1978. I
wonder what else he used it for.

Anyway, the final task is to copy this
from the scratch buffer to zero page.
I want to copy it at the same time as
the original disk (so nothing in zero
page gets overwritten while loading
DOS), but the act of loading DOS will
overwrite my scratch buffer. Argh.

So we will do it in two stages.

Stage 1: immediately after loading the
RWTS (and scratch buffer), copy the
scratch buffer to $0800.

                 --v--

T00,S00,$B3 (loaded at $76B3):
----------- DISASSEMBLY MODE ----------
; note: this routine must preserve X
; because $7700 assumes it contains
; the boot slot x16
; A & Y can be clobbered, no one cares
00B3:A0 00          LDY   #$00
00B5:B9 00 7B       LDA   $7B00,Y
00B8:99 00 08       STA   $0800,Y
00BB:C8             INY
00BC:D0 F7          BNE   $00B5
00BE:4C 00 77       JMP   $7700

                 --^--

Now to call this routine instead of
jumping to $7700:

T00,S00,$4A: 6C FD 08 -> 4C B3 76

Now to use the buffer at $0800 instead
of $7ED0, later in the boot. That copy
loop was at $6283, which is loaded from
track 0, sector 3.

T01,S03,$86: D0 7E -> 00 08

And that's it. I can leave the wacky
"print a space" logic -- in fact, I
must, because that's how we continue
the boot after copying zero page.

]PR#6
...boots and loads and it is glorous...

After extensive testing, there does not
appear to be any further protection.

Quod erat liberandum.

                   ~

               Changelog


2021-12-22

- typos and corrected information on
  the modified prologues and epilogues

2020-07-01

- typos

2020-04-11

- typos (thanks Andrew R.)

2020-04-10

- initial release

---------------------------------------
A 4am crack                    No. 2181
------------------EOF------------------
